9.9.2. Client-Side Programming
My HeaderClientBase<T,H> proxy base class is designed to
automate passing information in the headers from the client to the
service:
public abstract class InterceptorClientBase<T> : ClientBase<T> where T : class
{
protected virtual void PreInvoke(ref Message request)
{}
//More members
}
public abstract partial class HeaderClientBase<T,H> : InterceptorClientBase<T>
where T : class
{
public H Header
{get;protected set;}
public HeaderClientBase(H header);
public HeaderClientBase(H header,string endpointName);
//More members
}
However, when it comes to the response context, there are
differences compared with the raw headers management: specifically, changing the
context (that is, the headers) on each call as opposed to setting it
only at construction time, generating method IDs and providing them to
the client, and enqueuing rather than merely invoking the service
call. While the client can easily use my HeaderClientBase<T,H> to do all that,
all clients will have to repeat such code for every contract and proxy
they have. It is better to automate and encapsulate these steps in a
dedicated proxy base class such as my ClientResponseBase<T>, shown in Example 3.
Example 3. The ClientResponseBase<T> class
public abstract class ClientResponseBase<T> :
HeaderClientBase<T,ResponseContext> where T : class
{
protected readonly string ResponseAddress;
public ClientResponseBase(string responseAddress)
{
ResponseAddress = responseAddress;
Endpoint.VerifyQueue();
}
public ClientResponseBase(string responseAddress,string endpointName)
{...}
public ClientResponseBase(string responseAddress,
NetMsmqBinding binding,EndpointAddress address)
{...}
/* More constructors */
protected override void PreInvoke(ref Message request)
{
string methodId = GenerateMethodId();
Header = new ResponseContext(ResponseAddress,methodId);
base.PreInvoke(ref request);
}
protected virtual string GenerateMethodId()
{
return Guid.NewGuid().ToString();
}
}
|
The constructors of ClientResponseBase<T> accept the
response address and the regular proxy parameters, such as the
endpoint name, address, and binding. The constructors store the
response address in the read-only public field ResponseAddress. In addition, the
constructors use the VerifyQueue()
endpoint extension method to verify that the service queue (and the
DLQ) exists and to create it if necessary.
ClientResponseBase<T>
provides the virtual GenerateMethodId() method, which by default
uses a GUID for the method ID. However, your subclasses of ClientResponseBase<T> can override it
and provide their own unique strings, such as an incremented
integer.
The heart of ClientResponseBase<T> is the
overridden PreInvoke() method.
PreInvoke() is
defined as virtual in the InterceptorClientBase<T> base class of
HeaderClientBase<T,H>.
InterceptorClientBase<T> is
part of a generic interception framework I wrote that enables you to perform custom
pre-call and post-call interception steps. For every operation invoked
by the client, PreInvoke()
generates a new method ID, provides it to a new ResponseContext object (along with the
response address supplied to the constructor), and assigns the new
ResponseContext object to the
Header property of HeaderClientBase<T,H>. Thanks to
generics, Header is of the type
ResponseContext.
2.1. Using ClientResponseBase<T>
You use ClientResponseBase<T> like a regular
proxy; for example, given this calculator contract:
[ServiceContract]
interface ICalculator
{
[OperationContract(IsOneWay = true)]
void Add(int number1,int number2);
//More operations
}
Example 4 shows
the matching service proxy.
Example 4. Deriving from ClientResponseBase<T>
class CalculatorClient : ClientResponseBase<ICalculator>,ICalculator
{
public CalculatorClient(string responseAddress) : base(responseAddress)
{}
public CalculatorClient(string responseAddress,string endpointName) :
base(responseAddress,endpointName)
{}
public CalculatorClient(string responseAddress,
NetMsmqBinding binding,EndpointAddress address) :
base(responseAddress,binding,address)
{}
//More constructors
public void Add(int number1,int number2)
{
Channel.Add(number1,number2);
}
//More operations
}
|
Using the proxy in Example 9-24 yields this
straightforward client code:
string responseAddress = "net.msmq://localhost/private/MyCalculatorResponseQueue";
CalculatorClient proxy = new CalculatorClient(responseAddress);
proxy.Add(2,3);
proxy.Close();
Note how closely the client that provides the response
address to the proxy corresponds to a client that provides a duplex
callback object to a proxy. In the queued services world, the response
service address is the equivalent callback reference.
Note:
A queued response service is not limited to being used only
with a queued service. You can use the same technique to pass the
address and method ID to a connected service and have that service
respond to a client-provided queued response service. You will
need to rework ClientResponseBase<T> so that it uses only
Binding.
When managing the responses on the client side using a
ClientResponseBase<T>-derived proxy,
it is often very handy to have the invoking client obtain the method
ID used to dispatch the call. You can do this easily with the
Header property:
CalculatorClient proxy = new CalculatorClient(responseAddress);
proxy.Add(2,3);
string methodId = proxy.Header.MethodId;
proxy.Close();